import sys

import cv2
import numpy as np
from pynput.keyboard import Controller, Key
import time
import psutil
import win32gui
import win32process
import mss
import pyautogui  # 用于点击
import os # 导入 os 模块用于创建目录
import logging # 导入 logging 模块

# 配置日志系统，将日志输出到控制台和文件
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(levelname)s - %(message)s',
                    handlers=[
                        logging.StreamHandler(sys.stdout), # 输出到控制台
                        logging.FileHandler('game_automation.log', encoding='utf-8') # 输出到文件
                    ])

# --- 配置参数 ---
# 将配置参数定义为全局变量，以便函数内部可以访问
# HSV颜色范围
# 放宽的蓝色 HSV 范围 (基于原始 [100, 150, 120] 到 [130, 255, 255] 范围)
blue_lower = np.array([90, 80, 100]) # 降低了H, S, V的下限
blue_upper = np.array([140, 255, 255]) # 扩大了H的上限，保持S, V的上限

red_lower1 = np.array([0, 100, 100])
red_upper1 = np.array([10, 255, 255])
red_lower2 = np.array([160, 100, 100])
red_upper2 = np.array([180, 255, 100]) # 修正 red_upper2 的V值范围，原为255, 255，可能过宽

# 判定线设置
judge_line_x_blue = 796
tolerance_blue = 10
judge_line_x_red = 796
tolerance_red = 10

# 尺寸过滤
min_object_size = 20
min_object_area = 500

# ROI 区域 (相对于游戏窗口左上角)
roi_x = 770
roi_y = 217
roi_width = 70
roi_height = 37

# 绘制颜色 (调试用)
draw_blue_color = (255, 0, 0)     # BGR 蓝色
draw_red_color = (0, 0, 255)      # BGR 红色
draw_line_color = (0, 255, 0)     # BGR 绿色
draw_roi_color = (255, 255, 0)    # BGR 青色
line_thickness = 1
point_radius = 3

# 按键间隔
min_press_delay = 0.6

# exit_play.png 模板匹配参数
# **优化点1：预加载灰度模板，提高模板匹配鲁棒性**
# 加载模板时就转换为灰度图，避免运行时重复转换，提高匹配精度
try:
    exit_template_color = cv2.imread("templates/exit_play.png", cv2.IMREAD_COLOR)
    if exit_template_color is not None:
        exit_template_gray = cv2.cvtColor(exit_template_color, cv2.COLOR_BGR2GRAY)
        logging.info("成功加载 templates/exit_play.png 模板 (彩色和灰度)。")
    else:
        exit_template_gray = None
        logging.warning("警告：无法加载模板图像 templates/exit_play.png，退出按钮检测将无效。")
except Exception as e:
    exit_template_gray = None
    logging.error(f"加载模板图像时发生错误: {e}，退出按钮检测将无效。")
    exit_template_color = None # 如果彩色模板加载失败，也设置为 None


exit_threshold = 0.7 # 模板匹配阈值
# diff_threshold = 15  # 图像变化判定阈值 (当前主循环中未使用)

# --- 调试模式配置 ---
# 控制是否开启调试模式，开启后会在按下按键时保存全窗口图片并在上面标记
DEBUG_MODE = False # 设置为 True 开启调试模式

# 调试图片保存路径
DEBUG_IMAGES_DIR = "debug_images"

# --- 小游戏超时设置 ---
# 小游戏超时时间 (秒)，超时后强制进行退出按钮检测
MINI_GAME_TIMEOUT = 25

# --- 辅助函数 ---

# **优化点2：统一路径管理，更鲁棒地找到模板目录**
# 确保在打包成exe或直接运行.py文件时都能正确找到 templates 目录
if getattr(sys, 'frozen', False):
    # 如果是打包的exe文件
    BASE_DIR = os.path.dirname(sys.executable)
else:
    # 如果是普通的.py文件
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# 重新定义 TEMPLATE_DIR，使其指向 BASE_DIR 下的 templates
TEMPLATE_DIR = os.path.join(BASE_DIR, "templates")

def template_path(filename):
    """构建模板图像的完整路径。"""
    return os.path.join(TEMPLATE_DIR, filename)

def find_window_by_exe(exe_name):
    """通过可执行文件名查找窗口句柄。"""
    def callback(hwnd, result):
        try:
            _, pid = win32process.GetWindowThreadProcessId(hwnd)
            proc = psutil.Process(pid)
            if proc.name().lower() == exe_name.lower() and win32gui.IsWindowVisible(hwnd):
                result.append(hwnd)
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            pass
    hwnds = []
    win32gui.EnumWindows(callback, hwnds)
    return hwnds[0] if hwnds else None

def get_window_rect(hwnd):
    """获取窗口的矩形区域。"""
    try:
        left, top, right, bottom = win32gui.GetWindowRect(hwnd)
        # 排除窗口边框和标题栏，只获取客户区（游戏画面）
        # win32gui.GetClientRect 和 win32gui.ClientToScreen 是更精确的获取客户区方法
        # 但对于全屏或无边框窗口，GetWindowRect 通常足够准确，且更简单。
        # 如果游戏窗口有标准边框，且匹配出现问题，可以考虑更精确的方案。
        return (left, top, right - left, bottom - top)

    except Exception as e:
        logging.error(f"获取窗口矩形时发生错误: {e}")
        return None

def in_range_hsv(img, lower, upper):
    """根据给定的 HSV 范围创建掩码。"""
    # 检查图像是否为空
    if img is None or img.size == 0:
         logging.debug("警告: 接收到空图像，无法进行 HSV 范围检测。")
         return None # 返回 None 或者一个空的掩码

    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    return cv2.inRange(hsv, lower, upper)

def near_judge_line(x, target_x, tol):
    """判断一个 x 坐标是否在目标 x 坐标的容差范围内。"""
    return abs(x - target_x) < tol

# --- 主要功能函数 ---

def run_game_automation(game_exe_name="AetherGazer.exe"):
    """
    运行游戏自动化脚本。

    此函数会查找指定的游戏窗口，然后循环进行屏幕截图、图像处理
    （颜色检测和模板匹配）以及模拟按键和鼠标点击。
    脚本会持续运行直到游戏窗口关闭或发生错误。

    Args:
        game_exe_name (str): 要查找的游戏可执行文件名，默认为 "AetherGazer.exe"。
    """
    # 在函数内部初始化 Pynput 和 Mss
    keyboard = Controller()
    sct = mss.mss()

    # 初始化游戏窗口
    hwnd = find_window_by_exe(game_exe_name)
    if hwnd is None:
        logging.error(f"错误：未找到游戏窗口 {game_exe_name}，请确认游戏是否运行。")
        return False # 返回 False 表示启动失败

    logging.info(f"已找到游戏窗口 {game_exe_name}，开始运行脚本...")

    # 局部变量用于控制循环和状态
    template_check_interval = 5 # 模板检测间隔（秒）
    last_template_check = time.time() # 上次模板检测时间
    template_clicked = False # 标记是否已点击过退出按钮

    last_press_time = 0 # 上次按键时间

    # 标记小游戏开始时间 (假设进入主循环即为小游戏开始)
    mini_game_start_time = time.time()
    logging.info(f"小游戏超时时间设置为：{MINI_GAME_TIMEOUT} 秒")


    # 如果开启调试模式，创建保存图片的目录
    if DEBUG_MODE:
        os.makedirs(DEBUG_IMAGES_DIR, exist_ok=True)
        logging.info(f"调试模式已开启，图片将保存到：{DEBUG_IMAGES_DIR}")

    # 主自动化循环
    try:
        while True:
            # 检查游戏窗口是否仍然有效且可见
            if not win32gui.IsWindow(hwnd) or not win32gui.IsWindowVisible(hwnd):
                logging.info(f"游戏窗口 {game_exe_name} 已关闭或不可见，退出脚本。")
                break # 窗口关闭或不可见时退出循环

            # 检查当前前台窗口是否是目标游戏窗口
            foreground = win32gui.GetForegroundWindow()
            if foreground != hwnd:
                logging.debug("警告: 游戏窗口不是前台窗口，等待...")
                time.sleep(0.1) # 如果不是前台，等待一小段时间再继续
                continue # 跳过当前循环，等待窗口回到前台

            # 获取窗口位置和大小
            window_rect = get_window_rect(hwnd)
            if window_rect is None:
                logging.error("错误：无法获取窗口矩形区域，退出脚本。")
                break # 如果无法获取窗口矩形，退出循环

            left, top, width, height = window_rect
            monitor = {"top": top, "left": left, "width": width, "height": height}

            # 截取屏幕
            try:
                sct_img = sct.grab(monitor)
                frame = np.array(sct_img)
                frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR)
            except mss.exception.ScreenShotError as e:
                 logging.error(f"屏幕截图错误: {e}. 窗口可能已移动或关闭。")
                 break # 截图失败时退出循环
            except Exception as e:
                 logging.error(f"屏幕截图时发生未知错误: {e}. 退出脚本。")
                 break # 捕获其他可能的截图错误


            current_time = time.time()
            elapsed_time = current_time - mini_game_start_time

            # 模板匹配检测退出按钮
            # 只有当灰度模板加载成功且之前未点击过时才进行检测
            # 同时，如果小游戏超时，无论上次检测间隔是否到达，都强制检测一次
            if exit_template_gray is not None and not template_clicked and \
               ((current_time - last_template_check) >= template_check_interval or elapsed_time >= MINI_GAME_TIMEOUT):

                last_template_check = current_time # 即使是超时触发，也更新上次检测时间，避免连续每帧检测
                if elapsed_time >= MINI_GAME_TIMEOUT:
                     logging.info(f"小游戏超时 ({elapsed_time:.2f} 秒)，强制检测退出按钮...")
                     # 如果是超时触发，可以在这里选择增加一些日志或特殊处理

                # **优化点3：在匹配前将当前帧转换为灰度图**
                # 确保截取的帧有效且足够大，可以进行模板匹配
                if frame is not None and frame.shape[0] >= exit_template_gray.shape[0] and frame.shape[1] >= exit_template_gray.shape[1]:
                    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 实时截图转换为灰度图
                    result = cv2.matchTemplate(frame_gray, exit_template_gray, cv2.TM_CCOEFF_NORMED)
                    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)

                    # **优化点4：调试模式下保存未匹配到时的图片，并显示最大匹配值**
                    if DEBUG_MODE and max_val < exit_threshold:
                        timestamp = int(time.time() * 1000)
                        filename = os.path.join(DEBUG_IMAGES_DIR, f"nomatch_exit_button_{timestamp}_maxval{max_val:.2f}.png")
                        try:
                            # 绘制ROI区域，以便查看是否有部分匹配
                            debug_frame_nomatch = frame.copy()
                            # 可以在这里绘制一个矩形，表示模板搜索的区域（如果需要）
                            # 例如，如果你的模板搜索范围是整个屏幕，那就不需要特定ROI
                            # 如果你想高亮最大匹配点，可以绘制一个矩形：
                            # if max_loc is not None:
                            #     cv2.rectangle(debug_frame_nomatch, max_loc, (max_loc[0] + exit_template_gray.shape[1], max_loc[1] + exit_template_gray.shape[0]), (0, 255, 255), 2)
                            cv2.imwrite(filename, debug_frame_nomatch)
                            logging.debug(f"未匹配到退出按钮，已保存调试图片: {filename} (max_val={max_val:.2f})")
                        except Exception as save_e:
                            logging.warning(f"保存调试图片 {filename} 失败: {save_e}")

                    if max_val >= exit_threshold:
                        # 计算鼠标点击位置 (屏幕绝对坐标)
                        click_x = left + max_loc[0] + exit_template_gray.shape[1] // 2
                        click_y = top + max_loc[1] + exit_template_gray.shape[0] // 2

                        logging.info(f"匹配成功，准备点击退出按钮 ({click_x}, {click_y})，max_val={max_val:.2f}")
                        # 在点击前等待一小段时间，确保窗口已准备好接收点击
                        time.sleep(3) # 保持这个等待，确保游戏反应过来
                        try:
                            pyautogui.click(click_x, click_y)
                            logging.info("已执行鼠标点击")
                            template_clicked = True # 设置标记，防止重复点击

                            # 点击后模拟按 ESC 键
                            time.sleep(1) # 保持等待
                            keyboard.press(Key.esc)
                            keyboard.release(Key.esc)
                            logging.info("已按下 ESC 键 (第一次)")
                            time.sleep(1) # 保持等待
                            keyboard.press(Key.esc)
                            keyboard.release(Key.esc)
                            logging.info("已按下 ESC 键 (第二次)")
                            return # 点击退出并按ESC后，任务通常结束，退出函数


                        except Exception as click_error:
                            logging.error(f"执行鼠标点击时发生错误: {click_error}")
                            # 点击失败，重置模板点击标记，让脚本继续尝试或等待下一次超时
                            template_clicked = False # 允许再次尝试点击
                    else:
                        logging.info(f"未匹配到退出按钮，最大匹配值为 {max_val:.2f}，小于阈值 {exit_threshold}")
                else:
                    logging.debug("警告: 帧尺寸小于模板尺寸或帧无效，跳过模板匹配。")


            # --- 以下是原有的颜色检测和按键逻辑 ---
            # ROI 提取和颜色检测
            # 确保 ROI 坐标和尺寸在帧的有效范围内 (在frame坐标系内)
            roi_x_safe = max(0, roi_x)
            roi_y_safe = max(0, roi_y)
            # min(roi_width, frame.shape[1] - roi_x_safe) 更安全，但frame尺寸就是窗口尺寸，所以 min(roi_width, width - roi_x_safe) 也行
            roi_width_safe = min(roi_width, frame.shape[1] - roi_x_safe)
            roi_height_safe = min(roi_height, frame.shape[0] - roi_y_safe)


            # 检查计算出的 ROI 区域是否有效
            if roi_width_safe <= 0 or roi_height_safe <= 0:
                 logging.debug(f"计算的ROI区域无效: x={roi_x_safe}, y={roi_y_safe}, w={roi_width_safe}, h={roi_height_safe}. 跳过此帧的颜色检测。")
                 time.sleep(0.01) # 短暂等待，避免占用过多 CPU
                 continue # 跳过当前帧的颜色检测

            # 提取 ROI 区域
            roi_frame = frame[roi_y_safe: roi_y_safe + roi_height_safe, roi_x_safe: roi_x_safe + roi_width_safe]

            # 再次检查提取后的 ROI 帧是否为空
            if roi_frame is None or roi_frame.size == 0:
                logging.debug("警告: 提取的 ROI 帧为空，跳过此帧的颜色检测。")
                time.sleep(0.001)
                continue

            # 在 ROI 区域内进行颜色检测
            mask_blue = in_range_hsv(roi_frame, blue_lower, blue_upper)
            mask_red1 = in_range_hsv(roi_frame, red_lower1, red_upper1)
            mask_red2 = in_range_hsv(roi_frame, red_lower2, red_upper2)
            # 检查掩码是否 None (in_range_hsv 可能会返回 None 如果输入图像为空)
            if mask_blue is None or mask_red1 is None or mask_red2 is None:
                 logging.debug("警告: HSV 掩码生成失败，跳过颜色检测。")
                 time.sleep(0.001)
                 continue # 跳过颜色检测部分

            mask_red = cv2.bitwise_or(mask_red1, mask_red2)

            key_pressed_this_frame = False # 标记当前帧是否已经按下了键

            # 蓝色对象检测
            # 在 ROI 的掩码上查找轮廓
            # 检查 mask_blue 是否为空
            if mask_blue.size > 0:
                contours_blue, _ = cv2.findContours(mask_blue, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
                for cnt_roi in contours_blue:
                    # 获取轮廓的包围矩形 (在 ROI 坐标系内)
                    x_roi, y_roi, w, h = cv2.boundingRect(cnt_roi)
                    # 根据尺寸过滤对象
                    if w < min_object_size or h < min_object_size or w * h < min_object_area:
                        continue

                    # 计算对象中心点的 x 坐标 (在 ROI 坐标系内)
                    center_x_roi = x_roi + w // 2
                    # 将 ROI 坐标转换为屏幕绝对坐标，用于与判定线比较 (实际是frame坐标)
                    actual_center_x = roi_x_safe + center_x_roi

                    # 判断对象中心是否靠近蓝色判定线
                    if near_judge_line(actual_center_x, judge_line_x_blue, tolerance_blue):
                        # 检查是否满足按键间隔，并且当前帧还未按键
                        if not key_pressed_this_frame and (current_time - last_press_time >= min_press_delay):
                            last_press_time = current_time
                            keyboard.press('u')
                            keyboard.release('u')
                            logging.info("按下 U")
                            key_pressed_this_frame = True # 标记当前帧已按键

                            # --- 调试模式：保存全窗口图片并标记 ---
                            if DEBUG_MODE and frame is not None:
                                timestamp = int(time.time() * 1000)
                                filename = os.path.join(DEBUG_IMAGES_DIR, f"blue_detected_U_{timestamp}.png")
                                try:
                                    # 在全窗口图片上绘制标记
                                    debug_frame = frame.copy()
                                    # 绘制 ROI 区域
                                    cv2.rectangle(debug_frame, (roi_x_safe, roi_y_safe), (roi_x_safe + roi_width_safe, roi_y_safe + roi_height_safe), draw_roi_color, line_thickness)
                                    # 绘制检测到的蓝色对象轮廓 (坐标转换为frame坐标)
                                    obj_x_frame = roi_x_safe + x_roi
                                    obj_y_frame = roi_y_safe + y_roi
                                    cv2.rectangle(debug_frame, (obj_x_frame, obj_y_frame), (obj_x_frame + w, obj_y_frame + h), draw_blue_color, line_thickness)
                                    # 绘制对象中心点 (坐标转换为frame坐标)
                                    obj_center_x_frame = roi_x_safe + center_x_roi
                                    obj_center_y_frame = roi_y_safe + y_roi + h // 2
                                    cv2.circle(debug_frame, (obj_center_x_frame, obj_center_y_frame), point_radius, draw_blue_color, -1)
                                     # 绘制判定线 (在frame坐标系内)
                                    cv2.line(debug_frame, (judge_line_x_blue, 0), (judge_line_x_blue, frame.shape[0]), draw_line_color, line_thickness)

                                    cv2.imwrite(filename, debug_frame)
                                    logging.debug(f"已保存调试图片: {filename}")
                                except Exception as save_e:
                                    logging.warning(f"保存调试图片 {filename} 失败: {save_e}")
                            # --- 调试模式结束 ---

                            break # 如果按了键，就处理下一个帧，不再检测同一种颜色

            # 红色对象检测 (只有当前帧还没有按键时才进行红色检测)
            if not key_pressed_this_frame:
                 # 检查 mask_red 是否为空
                 if mask_red.size > 0:
                    # 在 ROI 的掩码上查找轮廓
                    contours_red, _ = cv2.findContours(mask_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
                    for cnt_roi in contours_red:
                        # 获取轮廓的包围矩形 (在 ROI 坐标系内)
                        x_roi, y_roi, w, h = cv2.boundingRect(cnt_roi)
                        # 根据尺寸过滤对象
                        if w < min_object_size or h < min_object_size or w * h < min_object_area:
                            continue

                        # 计算对象中心点的 x 坐标 (在 ROI 坐标系内)
                        center_x_roi = x_roi + w // 2
                        # 将 ROI 坐标转换为屏幕绝对坐标，用于与判定线比较 (实际是frame坐标)
                        actual_center_x = roi_x_safe + center_x_roi

                        # 判断对象中心是否靠近红色判定线
                        if near_judge_line(actual_center_x, judge_line_x_red, tolerance_red):
                             # 检查是否满足按键间隔，并且当前帧还未按键
                            if not key_pressed_this_frame and (current_time - last_press_time >= min_press_delay):
                                last_press_time = current_time
                                keyboard.press('i')
                                keyboard.release('i')
                                logging.info("按下 I")
                                key_pressed_this_frame = True # 标记当前帧已按键

                                # --- 调试模式：保存全窗口图片并标记 ---
                                if DEBUG_MODE and frame is not None:
                                    timestamp = int(time.time() * 1000)
                                    filename = os.path.join(DEBUG_IMAGES_DIR, f"red_detected_I_{timestamp}.png")
                                    try:
                                        # 在全窗口图片上绘制标记
                                        debug_frame = frame.copy()
                                        # 绘制 ROI 区域
                                        cv2.rectangle(debug_frame, (roi_x_safe, roi_y_safe), (roi_x_safe + roi_width_safe, roi_y_safe + roi_height_safe), draw_roi_color, line_thickness)
                                        # 绘制检测到的红色对象轮廓 (坐标转换为frame坐标)
                                        obj_x_frame = roi_x_safe + x_roi
                                        obj_y_frame = roi_y_safe + y_roi
                                        cv2.rectangle(debug_frame, (obj_x_frame, obj_y_frame), (obj_x_frame + w, obj_y_frame + h), draw_red_color, line_thickness)
                                        # 绘制对象中心点 (坐标转换为frame坐标)
                                        obj_center_x_frame = roi_x_safe + center_x_roi
                                        obj_center_y_frame = roi_y_safe + y_roi + h // 2
                                        cv2.circle(debug_frame, (obj_center_x_frame, obj_center_y_frame), point_radius, draw_red_color, -1)
                                         # 绘制判定线 (在frame坐标系内)
                                        cv2.line(debug_frame, (judge_line_x_red, 0), (judge_line_x_red, frame.shape[0]), draw_line_color, line_thickness)

                                        cv2.imwrite(filename, debug_frame)
                                        logging.debug(f"已保存调试图片: {filename}")
                                    except Exception as save_e:
                                        logging.warning(f"保存调试图片 {filename} 失败: {save_e}")
                                # --- 调试模式结束 ---

                                break # 如果按了键，就处理下一个帧

            # 短暂等待，控制循环速度
            time.sleep(0.01)

    except KeyboardInterrupt:
        # 允许用户通过 Ctrl+C 中断脚本
        logging.info("脚本被用户中断。")
    except Exception as e:
        # 捕获其他未知错误
        logging.error(f"脚本运行过程中发生错误: {e}")
        return False # 发生错误时返回 False

    logging.info("自动化脚本已停止。")
    return True # 正常结束时返回 True


# --- 脚本直接运行时执行的代码 ---
# 这个块只在直接运行 game_automator_module.py 文件时执行
# 如果这个文件被其他脚本导入，下面的代码不会运行
if __name__ == "__main__":
    logging.info("直接运行 game_automator_module.py 文件，开始自动化...")
    # 调用封装好的函数开始自动化过程
    # 你可以在这里指定要自动化的游戏进程名称
    success = run_game_automation("AetherGazer.exe")
    if not success:
        logging.info("自动化脚本未能成功运行或中途退出。")
    logging.info("脚本执行完毕。")